﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using SweetSQL.Data;

namespace SweetSQL.Extensions
{
    internal sealed class WhereEvaluator<TTable>
        where TTable : class, new()
    {
        private StringBuilder _builder = new StringBuilder();
        private int _index = 0;

        public IDbCommand Command = null;
        public IConnectionProvider ConnectionProvider = null;
        public ISyntaxProvider SyntaxProvider = null;
        public Table<TTable> Table = null;

        private void AddParameter(Type type, object value)
        {
            IDataParameter parameter = ConnectionProvider.CreateParameter(SyntaxProvider.CreateParameterName(_index), type, _index);
            parameter.Value = ConnectionProvider.ConvertToDatabase(type, value);
            Command.Parameters.Add(parameter);
            _index++;
        }

        public void Evaluate(Expression<Func<TTable, bool>> func)
        {
            BinaryExpression expression = func.Body as BinaryExpression;
            if (expression != null)
            {
                _builder.Append(" WHERE ");

                EvalBinary(expression);
            }
        }

        private void EvalUnknown(Expression expression)
        {
            if (expression is BinaryExpression)
            {
                EvalBinary(expression as BinaryExpression);
            }
            else if (expression is ConstantExpression)
            {
                EvalConstant(expression as ConstantExpression);
            }
            else if (expression is MemberExpression)
            {
                EvalMember(expression as MemberExpression);
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        private void EvalConstant(ConstantExpression expression)
        {
            _builder.Append(SyntaxProvider.CreateParameterName(_index));
            AddParameter(expression.Type, expression.Value);
        }

        private void EvalMember(MemberExpression expression)
        {
            ColumnInfo column = null;
            if (expression.Member is PropertyInfo && Table.TableInfo.ColumnLookup.TryGetValue(expression.Member.Name, out column))
            {
                SyntaxProvider.AppendColumn(_builder, column);
            }
            else
            {
                _builder.Append(SyntaxProvider.CreateParameterName(_index));

                Type type = null;
                object value = null;
                switch (expression.Member.MemberType)
                {
                    case MemberTypes.Field:
                        FieldInfo fieldInfo = expression.Member as FieldInfo;
                        type = fieldInfo.FieldType;
                        if (fieldInfo.IsStatic)
                        {
                            value = fieldInfo.GetValue(null);
                        }
                        else
                        {
                            throw new NotImplementedException();
                        }
                        break;
                    case MemberTypes.Property:
                        PropertyInfo propertyInfo = expression.Member as PropertyInfo;
                        type = propertyInfo.PropertyType;
                        value = propertyInfo.GetValue(null, null);
                        break;
                    default:
                        break;
                }

                AddParameter(type, value);
            }
        }

        private void EvalBinary(BinaryExpression expression)
        {
            EvalUnknown(expression.Left);

            switch (expression.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    _builder.Append(" AND ");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    _builder.Append(" OR ");
                    break;
                case ExpressionType.Equal:
                    if (IsComparingWithNull(expression))
                    {
                        _builder.Append(" IS ");
                    }
                    else
                    {
                        _builder.Append(" = ");
                    }
                    break;
                case ExpressionType.GreaterThan:
                    _builder.Append(" > ");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    _builder.Append(" >= ");
                    break;
                case ExpressionType.LessThan:
                    _builder.Append(" < ");
                    break;
                case ExpressionType.LessThanOrEqual:
                    _builder.Append(" <= ");
                    break;
                case ExpressionType.NotEqual:
                    _builder.Append(" <> ");
                    break;
                default:
                    break;
            }

            EvalUnknown(expression.Right);
        }

        public override string ToString()
        {
            return _builder.ToString();        
        }

        private static bool IsNullConstant(ConstantExpression expression)
        {
            return (expression != null && expression.Value == null);
        }

        private bool IsComparingWithNull(BinaryExpression expression)
        {
            if (expression.NodeType == ExpressionType.Equal || expression.NodeType == ExpressionType.NotEqual)
            {
                if (IsNullConstant(expression.Left as ConstantExpression) ||
                    IsNullConstant(expression.Right as ConstantExpression))
                {
                    return true;
                }
            }
            return false;
        }
    }
}
